Pythonチュートリアル: オプション解析モジュール
https://gyazo.com/153a339305d78fc4fa4850753e4b1594
はじめに
python のコマンドラインアプリケーションを実行するときは、通常は次のように’コマンドラインから実行します。
code: bash
$ python myapp.py
このアプリケーションがあるファイルに何らかの処理をするためののものであったとします。 ファイルパスがプログラム中にハードコーディングされてしまっていると、ユーザ(自分自身も含めて)は異なるファイルに対して処理したいときは、その都度エディタを開いてプログラムを修正することになります。どう考えても非効率ですし、使い勝手が良いものではありませんよね。
こうした場合は、次のようなコマンドラインオプションを与えることで、対象ファイルを指定できるインタフェースを提供することが対策のひとつになります。
code: bash
$ python myapp.py --file=other_files
ここでは、コマンドラインでのオプションや引数を解析して、ユーザーインターフェースを設計するためのツールを紹介します。
ユーザインターフェース
コマンドラインインタフェース(CLI: Command Line Interface) はコマンドラインからパラメタを与えて、コマンドに処理内容を指示するものです。
Python にはCLIアプリケーションの開発を手助けしてくれる便利なモジュールやフレームワークが多数あるので、その一部をここで紹介することにします。
まず、大まかに次のように分類できるでしょう。
ヘルパーモジュール:オプション解析処理を手助けすることを目的としたモジュール
argparse: python の標準モジュール。メソッドでオプションを追加するように実装する。 click:関数デコレータで実装する。データ検証時に便利なサポートモジュールなが多数。 自動処理モジュール:オプション解析に関連する処理を自動的に行うモジュール
docopt:ファイルのdocstrings からCLI処理を自動生成する。 plac:関数の引数を解釈してCLI処理を自動生成する。 invoke: タスクランナーのためのモジュールだが、CLI処理やサブコマンド実行の機能がある。 コンソールアプリケーションを作成するためのフレームワーク
cmdkit:argparse を派生させて、コンソールアプリケーションに必要ないくつかの共通パターンを実装したもの。 argparse
argparse は Python の標準ライブラリなので、追加でモジュールをインストールしなくてもCLIアプリケーションを開発することができます。 はじめに生成したパーサーインスタンスのメソッドをオプション解析処理を追加するように実装します。
オプションの数が増えるにともない定義する行数が増えてしまうため、コードの判読性が低下してしまう欠点があります。
Click
Click は Webアプリケーションを開発するときのマイクロフレームワーク Flask と同じThe Pallets Projects が開発保守しているプロジェクトです。 関数をデコレータすることでコマンドライン解析処理を追加できるため、
もとの関数はほとんど修正することなくCLIアプリケーションにすることができます。
Click もオプションの数が増えるにともない、デコレータも多数になり関数で実際に変数を定義している箇所との物理的に距離(行数)が離れてしまうため、コードの判読性が低下してしまう欠点があります。
それでも、サポートライブラリを使用するとIPアドレスなど、オプション解析時に検証できるの型が多数追加されるため、コードがとても簡潔になります。
Typer
typer はWebアプリケーションを開発するときのマイクロフレームワーク FastAPI の作者 Sebastián Ramírez が開発保守しているプロジェクトです。 内部的には Click を使用してますが、Typer ではPython3.6から導入された変数のタイプヒントを利用してコマンドライン解析処理を実装することができます。
タイプヒントを使用しているため、オプションや引数が増えてもコードがスッキリ読みやすくなります。反面、既存の関数については定義を修正するか、ラッパー関数を定義する必要があることと、使用できるPython のバージョンに制限があります。
また、可変長キーワード引数で受け取るような関数には直接適用できません。
それでも、個人的にはいちばんオススメしたいモジュールです。
docopt
docopt は docscritings に記述した内容をパースしてオプション解析処理を生成してくれる拡張モジュールです。 docstrings の記述方法について、慣れるまで若干の学習コストが必要になります。
Plac
Plac は与えた関数を読み取ってオプションや引数の解析を自動的に処理してくれるため、ほとんどコードを追加をすることなく既存の関数やモジュールをCLIアプリケーションにすることができます。 また、コマンドラインを数回オプションを変えて実行したり、フルスクリーンでのアプリケーションの開発や、関数を並列実行させたりすることもできます。
完全に自動処理となるためヘルプメッセージなどで自由度が低く柔軟性がないことが欠点となります。
CLIアプリケーションを開発するというより、既存コードやモジュールのデバッグ、テストの自動処理に工数をかけたくないようなときに便利だと考えています。
Python-Fire
Python-Fireは、GoogleがリリースしたPythonオブジェクトからコマンドラインインターフェイスを自動的に生成するためのライブラリです。 コマンドラインからPythonコードをより簡単にデバッグし、既存のコードをCLIアプリケーションにすることができます。 Invoke
Invoke の本来の機能は、Python からコマンドを実行するためのタスクランナーなのですが、既存コードにわずかな修正をするだけでCLIアプリケーションにすることができます。 cmdkit
cmdkit:argparse を派生させて、コンソールアプリケーションに必要ないくつかの共通パターンを実装したもの。クラス定義としてCLIを構築してゆくため非常に柔軟で開発がしやすく、小規模なコンソールツールから、git のようなサブコマンドを持つ複雑なアプリケーションもカバーしている。 コマンド・インジェクションへの対応
CLI を作成する場合、ユーザーは文字通り実行可能なコードを含むあらゆるものを入力することができるため、ユーザー入力の処理に非常に注意する必要があります。コマンド・インジェクション(Command Injection) は、悪意のあるコードを文字列として与える攻撃で、SQL インジェクションと同様にプログラム開発者は予測して避けなければなりません。
例えば、次のコードは eval() を使ってユーザーが入力した式を電卓のように動的に評価するものです。
code: vulnerability.p
import typer
import os
def main(
input: str = typer.Option(..., help="Your expression")
):
print(eval(input))
if __name__ == "__main__":
typer.run(main)
このプログラムを次のように実行してみてください。
code: bash
$ python vulnerability.py --input "3 * 200"
600
$ python vulnerability.py --input "os.system('date')"
Wed Mar 16 19:54:15 JST 2022
0
この例では実害がありませんが、もし、ユーザが "date" を "rm -rf $HOME" としていたらどうでしょう? (例えの話なので、決して実行しないようにしてください)
通常はユーザがこのような文字列を与えることはないはずですが、これをスクリプトで処理していて、何かしらの処理で生成した文字列を実行するような場合では、意図しないバグによりシステムが機能停止に陥ることはあり得ることなのです。
ユーザがCLIで指定したファイルを読み込むような場合も、そのファイルには悪意のあるコードが含まれている可能性があります。ファイルを読み込むライブラリに脆弱性がないか、使用方法が安全であるかを確認する必要があります。
例えば、 YAMLファイルをロードする場合では、yaml.load() の代わりに yaml.safe_load() を使用するようにします。
基本的的には、ユーザーの入力や指示したデータを動的に評価することは避けるべきです。